"""Unit tests for data models."""
import pytest
import uuid
from datetime import datetime
from pydantic import ValidationError
from app import db
from app.models import User, POI, Route, WalkingSession, Music
from app.models.location import Location
from app.models.weather_data import WeatherData


class TestUserModel:
    """Test User model creation and validation."""
    
    def test_user_creation(self, app):
        """Test creating a user with valid data."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid_123',
                nickname='测试用户',
                avatar_url='https://example.com/avatar.jpg',
                home_latitude=39.9042,
                home_longitude=116.4074,
                home_accuracy=10.0,
                preferences={'theme': 'light'}
            )
            db.session.add(user)
            db.session.commit()
            
            assert user.id is not None
            assert user.wechat_openid == 'test_openid_123'
            assert user.nickname == '测试用户'
            assert user.home_latitude == 39.9042
            assert user.home_longitude == 116.4074
            assert user.preferences == {'theme': 'light'}
            assert user.created_at is not None
    
    def test_user_unique_openid(self, app):
        """Test that wechat_openid must be unique."""
        with app.app_context():
            user1 = User(
                id=str(uuid.uuid4()),
                wechat_openid='duplicate_openid',
                nickname='User 1'
            )
            db.session.add(user1)
            db.session.commit()
            
            user2 = User(
                id=str(uuid.uuid4()),
                wechat_openid='duplicate_openid',
                nickname='User 2'
            )
            db.session.add(user2)
            
            with pytest.raises(Exception):  # Should raise IntegrityError
                db.session.commit()
    
    def test_user_to_dict(self, app):
        """Test user serialization to dictionary."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid',
                nickname='测试',
                home_latitude=39.9042,
                home_longitude=116.4074,
                home_accuracy=10.0
            )
            db.session.add(user)
            db.session.commit()
            
            user_dict = user.to_dict()
            assert user_dict['id'] == user.id
            assert user_dict['wechat_openid'] == 'test_openid'
            assert user_dict['home_location']['latitude'] == 39.9042
            assert user_dict['home_location']['longitude'] == 116.4074


class TestPOIModel:
    """Test POI model creation and validation."""
    
    def test_poi_creation(self, app):
        """Test creating a POI with valid data."""
        with app.app_context():
            poi = POI(
                id=str(uuid.uuid4()),
                name='天安门广场',
                latitude=39.9042,
                longitude=116.4074,
                category='历史',
                description='中国的象征性地标',
                photos=['https://example.com/photo1.jpg'],
                rating=4.8,
                tags=['历史', '文化', '地标'],
                external_id='tencent_123456'
            )
            db.session.add(poi)
            db.session.commit()
            
            assert poi.id is not None
            assert poi.name == '天安门广场'
            assert poi.latitude == 39.9042
            assert poi.longitude == 116.4074
            assert poi.category == '历史'
            assert poi.rating == 4.8
            assert len(poi.tags) == 3
    
    def test_poi_required_fields(self, app):
        """Test that POI requires name and location."""
        with app.app_context():
            poi = POI(
                id=str(uuid.uuid4()),
                name='Test POI',
                latitude=39.9042,
                longitude=116.4074
            )
            db.session.add(poi)
            db.session.commit()
            
            assert poi.name is not None
            assert poi.latitude is not None
            assert poi.longitude is not None
    
    def test_poi_to_dict(self, app):
        """Test POI serialization to dictionary."""
        with app.app_context():
            poi = POI(
                id=str(uuid.uuid4()),
                name='Test POI',
                latitude=39.9042,
                longitude=116.4074,
                category='历史',
                rating=4.5
            )
            db.session.add(poi)
            db.session.commit()
            
            poi_dict = poi.to_dict()
            assert poi_dict['name'] == 'Test POI'
            assert poi_dict['location']['latitude'] == 39.9042
            assert poi_dict['category'] == '历史'


class TestRouteModel:
    """Test Route model creation and validation."""
    
    def test_route_creation(self, app):
        """Test creating a route with valid data."""
        with app.app_context():
            poi = POI(
                id=str(uuid.uuid4()),
                name='Test POI',
                latitude=39.9100,
                longitude=116.4200
            )
            db.session.add(poi)
            db.session.commit()
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=39.9042,
                start_longitude=116.4074,
                end_latitude=39.9100,
                end_longitude=116.4200,
                waypoints=[
                    {'latitude': 39.9050, 'longitude': 116.4100},
                    {'latitude': 39.9080, 'longitude': 116.4150}
                ],
                distance_meters=1500.0,
                estimated_duration=20,
                target_poi_id=poi.id
            )
            db.session.add(route)
            db.session.commit()
            
            assert route.id is not None
            assert route.start_latitude == 39.9042
            assert route.end_latitude == 39.9100
            assert len(route.waypoints) == 2
            assert route.distance_meters == 1500.0
            assert route.estimated_duration == 20
    
    def test_route_poi_relationship(self, app):
        """Test route relationship with POI."""
        with app.app_context():
            poi = POI(
                id=str(uuid.uuid4()),
                name='Target POI',
                latitude=39.9100,
                longitude=116.4200
            )
            db.session.add(poi)
            db.session.commit()
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=39.9042,
                start_longitude=116.4074,
                end_latitude=39.9100,
                end_longitude=116.4200,
                target_poi_id=poi.id
            )
            db.session.add(route)
            db.session.commit()
            
            assert route.target_poi is not None
            assert route.target_poi.id == poi.id
            assert route.target_poi.name == 'Target POI'
    
    def test_route_to_dict(self, app):
        """Test route serialization to dictionary."""
        with app.app_context():
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=39.9042,
                start_longitude=116.4074,
                end_latitude=39.9100,
                end_longitude=116.4200,
                distance_meters=1500.0
            )
            db.session.add(route)
            db.session.commit()
            
            route_dict = route.to_dict()
            assert route_dict['start_location']['latitude'] == 39.9042
            assert route_dict['end_location']['longitude'] == 116.4200
            assert route_dict['distance_meters'] == 1500.0


class TestWalkingSessionModel:
    """Test WalkingSession model creation and validation."""
    
    def test_session_creation(self, app):
        """Test creating a walking session with valid data."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid',
                nickname='Test User'
            )
            db.session.add(user)
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=39.9042,
                start_longitude=116.4074,
                end_latitude=39.9100,
                end_longitude=116.4200
            )
            db.session.add(route)
            db.session.commit()
            
            session = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                route_id=route.id,
                start_time=datetime.utcnow(),
                visited_poi_ids=[],
                current_latitude=39.9042,
                current_longitude=116.4074,
                status='active',
                total_distance=0.0,
                generated_music_ids=[],
                route_history=[route.id]
            )
            db.session.add(session)
            db.session.commit()
            
            assert session.id is not None
            assert session.user_id == user.id
            assert session.route_id == route.id
            assert session.status == 'active'
            assert session.total_distance == 0.0
    
    def test_session_user_relationship(self, app):
        """Test session relationship with user."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid',
                nickname='Test User'
            )
            db.session.add(user)
            db.session.commit()
            
            session = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                status='active'
            )
            db.session.add(session)
            db.session.commit()
            
            assert session.user is not None
            assert session.user.id == user.id
            assert session.user.nickname == 'Test User'
    
    def test_session_route_relationship(self, app):
        """Test session relationship with route."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid'
            )
            db.session.add(user)
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=39.9042,
                start_longitude=116.4074,
                end_latitude=39.9100,
                end_longitude=116.4200
            )
            db.session.add(route)
            db.session.commit()
            
            session = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                route_id=route.id,
                status='active'
            )
            db.session.add(session)
            db.session.commit()
            
            assert session.route is not None
            assert session.route.id == route.id
    
    def test_session_to_dict(self, app):
        """Test session serialization to dictionary."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid'
            )
            db.session.add(user)
            db.session.commit()
            
            session = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                current_latitude=39.9042,
                current_longitude=116.4074,
                status='active',
                visited_poi_ids=['poi1', 'poi2']
            )
            db.session.add(session)
            db.session.commit()
            
            session_dict = session.to_dict()
            assert session_dict['user_id'] == user.id
            assert session_dict['status'] == 'active'
            assert len(session_dict['visited_poi_ids']) == 2
            assert session_dict['current_location']['latitude'] == 39.9042


class TestMusicModel:
    """Test Music model creation and validation."""
    
    def test_music_creation(self, app):
        """Test creating a music record with valid data."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid'
            )
            db.session.add(user)
            
            session = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                status='active'
            )
            db.session.add(session)
            
            poi = POI(
                id=str(uuid.uuid4()),
                name='Test POI',
                latitude=39.9042,
                longitude=116.4074
            )
            db.session.add(poi)
            db.session.commit()
            
            music = Music(
                id=str(uuid.uuid4()),
                user_id=user.id,
                session_id=session.id,
                poi_id=poi.id,
                file_url='https://example.com/music.mp3',
                duration=180,
                meta_info={
                    'weather': '晴',
                    'location': 'Test POI',
                    'generated_at': datetime.utcnow().isoformat()
                }
            )
            db.session.add(music)
            db.session.commit()
            
            assert music.id is not None
            assert music.user_id == user.id
            assert music.session_id == session.id
            assert music.poi_id == poi.id
            assert music.file_url == 'https://example.com/music.mp3'
            assert music.duration == 180
    
    def test_music_relationships(self, app):
        """Test music relationships with user, session, and POI."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid',
                nickname='Test User'
            )
            db.session.add(user)
            
            session = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                status='active'
            )
            db.session.add(session)
            
            poi = POI(
                id=str(uuid.uuid4()),
                name='Test POI',
                latitude=39.9042,
                longitude=116.4074
            )
            db.session.add(poi)
            db.session.commit()
            
            music = Music(
                id=str(uuid.uuid4()),
                user_id=user.id,
                session_id=session.id,
                poi_id=poi.id,
                file_url='https://example.com/music.mp3',
                duration=180
            )
            db.session.add(music)
            db.session.commit()
            
            # Test relationships
            assert music.user is not None
            assert music.user.id == user.id
            assert music.user.nickname == 'Test User'
            
            assert music.session is not None
            assert music.session.id == session.id
            
            assert music.poi is not None
            assert music.poi.id == poi.id
            assert music.poi.name == 'Test POI'
    
    def test_music_to_dict(self, app):
        """Test music serialization to dictionary."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid'
            )
            db.session.add(user)
            db.session.commit()
            
            music = Music(
                id=str(uuid.uuid4()),
                user_id=user.id,
                file_url='https://example.com/music.mp3',
                duration=180,
                meta_info={'weather': '晴'}
            )
            db.session.add(music)
            db.session.commit()
            
            music_dict = music.to_dict()
            assert music_dict['user_id'] == user.id
            assert music_dict['file_url'] == 'https://example.com/music.mp3'
            assert music_dict['duration'] == 180
            assert music_dict['metadata']['weather'] == '晴'


class TestLocationModel:
    """Test Location Pydantic model validation."""
    
    def test_location_creation(self):
        """Test creating a location with valid data."""
        location = Location(
            latitude=39.9042,
            longitude=116.4074,
            altitude=50.0,
            accuracy=10.0
        )
        
        assert location.latitude == 39.9042
        assert location.longitude == 116.4074
        assert location.altitude == 50.0
        assert location.accuracy == 10.0
    
    def test_location_latitude_validation(self):
        """Test latitude must be between -90 and 90."""
        with pytest.raises(ValidationError):
            Location(
                latitude=100.0,  # Invalid
                longitude=116.4074,
                accuracy=10.0
            )
        
        with pytest.raises(ValidationError):
            Location(
                latitude=-100.0,  # Invalid
                longitude=116.4074,
                accuracy=10.0
            )
    
    def test_location_longitude_validation(self):
        """Test longitude must be between -180 and 180."""
        with pytest.raises(ValidationError):
            Location(
                latitude=39.9042,
                longitude=200.0,  # Invalid
                accuracy=10.0
            )
        
        with pytest.raises(ValidationError):
            Location(
                latitude=39.9042,
                longitude=-200.0,  # Invalid
                accuracy=10.0
            )
    
    def test_location_accuracy_validation(self):
        """Test accuracy must be positive."""
        with pytest.raises(ValidationError):
            Location(
                latitude=39.9042,
                longitude=116.4074,
                accuracy=0.0  # Invalid
            )
        
        with pytest.raises(ValidationError):
            Location(
                latitude=39.9042,
                longitude=116.4074,
                accuracy=-10.0  # Invalid
            )


class TestWeatherDataModel:
    """Test WeatherData Pydantic model validation."""
    
    def test_weather_creation(self):
        """Test creating weather data with valid data."""
        weather = WeatherData(
            temperature=22.5,
            condition='晴',
            humidity=65,
            wind_speed=12.5,
            aqi=50,
            timestamp=datetime.utcnow()
        )
        
        assert weather.temperature == 22.5
        assert weather.condition == '晴'
        assert weather.humidity == 65
        assert weather.wind_speed == 12.5
        assert weather.aqi == 50
    
    def test_weather_humidity_validation(self):
        """Test humidity must be between 0 and 100."""
        with pytest.raises(ValidationError):
            WeatherData(
                temperature=22.5,
                condition='晴',
                humidity=150,  # Invalid
                wind_speed=12.5
            )
        
        with pytest.raises(ValidationError):
            WeatherData(
                temperature=22.5,
                condition='晴',
                humidity=-10,  # Invalid
                wind_speed=12.5
            )
    
    def test_weather_wind_speed_validation(self):
        """Test wind speed must be non-negative."""
        with pytest.raises(ValidationError):
            WeatherData(
                temperature=22.5,
                condition='晴',
                humidity=65,
                wind_speed=-5.0  # Invalid
            )
    
    def test_weather_aqi_validation(self):
        """Test AQI must be non-negative."""
        with pytest.raises(ValidationError):
            WeatherData(
                temperature=22.5,
                condition='晴',
                humidity=65,
                wind_speed=12.5,
                aqi=-10  # Invalid
            )


class TestModelRelationships:
    """Test relationships between models."""
    
    def test_user_sessions_relationship(self, app):
        """Test user can have multiple sessions."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid'
            )
            db.session.add(user)
            db.session.commit()
            
            session1 = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                status='completed'
            )
            session2 = WalkingSession(
                id=str(uuid.uuid4()),
                user_id=user.id,
                status='active'
            )
            db.session.add(session1)
            db.session.add(session2)
            db.session.commit()
            
            user_from_db = User.query.filter_by(id=user.id).first()
            assert user_from_db.sessions.count() == 2
    
    def test_user_music_library_relationship(self, app):
        """Test user can have multiple music records."""
        with app.app_context():
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid='test_openid'
            )
            db.session.add(user)
            db.session.commit()
            
            music1 = Music(
                id=str(uuid.uuid4()),
                user_id=user.id,
                file_url='https://example.com/music1.mp3'
            )
            music2 = Music(
                id=str(uuid.uuid4()),
                user_id=user.id,
                file_url='https://example.com/music2.mp3'
            )
            db.session.add(music1)
            db.session.add(music2)
            db.session.commit()
            
            user_from_db = User.query.filter_by(id=user.id).first()
            assert user_from_db.music_library.count() == 2
    
    def test_foreign_key_constraints(self, app):
        """Test foreign key constraints are enforced."""
        with app.app_context():
            # Try to create a session with non-existent user
            session = WalkingSession(
                id=str(uuid.uuid4()),
                user_id='non_existent_user_id',
                status='active'
            )
            db.session.add(session)
            
            with pytest.raises(Exception):  # Should raise IntegrityError
                db.session.commit()
